package main

import (
	"errors"
	"fmt"
	"math"
	"os"
)

//import (
//	. "fmt" // 那么可以在该文件内直接使用Printf,Println，而不用fmt.Printf,fmt.Println
//	f "fmt" // 那么使用为f.Printf,f.Println，相当于命名为fmt为f
//	_ "github.com/ziutek/mymysql/godrv" //_操作其实是引入该包，而不直接使用包里面的函数，而是调用了该包里面的init函数。
//)

/*
General:
%v 以默认的方式打印变量的值
%T 打印变量的类型
Integer:
%+d 带符号的整型，fmt.Printf("%+d", 255)输出+255
%q 打印单引号
%o 不带零的八进制
%#o 带零的八进制
%x 小写的十六进制
%X 大写的十六进制
%#x 带0x的十六进制
%U 打印Unicode字符
%#U 打印带字符的Unicode
%b 打印整型的二进制
Integer width:
%5d表示该整型最大长度是5
%-5d则相反，打印结果会自动左对齐
%05d会在数字前面补零。
Float：
%f (=%.6f) 6位小数点
%e (=%.6e) 6位小数点（科学计数法）
%g 用最少的数字来表示
%.3g 最多3位数字来表示
%.3f 最多3位小数来表示
String：
%s 正常输出字符串
%q 字符串带双引号，字符串中的引号带转义符
%#q 字符串带反引号，如果字符串内有反引号，就用双引号代替
%x 将字符串转换为小写的16进制格式
%X 将字符串转换为大写的16进制格式
% x 带空格的16进制格式
String Width (以5做例子）：
%5s 最小宽度为5
%-5s 最小宽度为5（左对齐）
%.5s 最大宽度为5
%5.7s 最小宽度为5，最大宽度为7
%-5.7s 最小宽度为5，最大宽度为7（左对齐）
%5.3s 如果宽度大于3，则截断
%05s 如果宽度小于5，就会在字符串前面补零
Struct：
%v 正常打印。比如：{sam {12345 67890}}
%+v 带字段名称。比如：{name:sam phone:{mobile:12345 office:67890}
%#v 用Go的语法打印。
比如main.People{name:”sam”, phone:main.Phone{mobile:”12345”, office:”67890”}}
Boolean：
%t 打印true或false
Pointer：
%p 带0x的指针
%#p 不带0x的指针
*/

// go中会自动调用每个文件的init和main函数，每个文件中最好只写一个init函数
// package为main的文件中必须定义main函数
func main() {
	fmt.Printf("Hello, world or 你好 or καλημ ́ρα κóσμ or こんにちはせかい\n")

	strModify()

	error()
	enum()
	arrays()
	slices()
	maps()
	switchs()
	funcs()
	defers()
	testFuncDef()
	panics()
	structs()
	hiddens()
	methods()
}

func strModify() {
	s := "hello"
	c := []byte(s)
	c[0] = 'b'
	s2 := string(c)
	fmt.Printf("%s\n", s2)

	s3 := "b" + s[1:]
	fmt.Printf("%s\n", s3)

	s4 := `hello world
				this is the day`
	fmt.Printf("%s\n", s4)
}

func error() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err.Error() != "" {
		fmt.Println(err)
	}
}

func groups() {
	// 分组声明
	const (
		i      = 100
		pi     = 3.1415
		prefix = "GO_"
	)
	// 相当于
	// const i = 100
	// const pi = 3.1415
	// const prefix = "GO_"
	//
	//var (
	//	i int
	//	pi float32
	//	prefix string
	//)
}

func enum() {
	const (
		x = iota // 0
		y = iota // 1
		z = iota // 2
		w        // 常量声明省略值时，默认和之前一个值字面相同，即 w = iota， 3
	)
	const v = iota // 每遇到一个const关键字，iota就会重置，此时为0
	const (
		h, i, j = iota, iota, iota // h = 0, i = 0, j = 0 在同一行值相同
	)
	const (
		a       = iota // 0
		b       = "B"
		c       = iota             // 2
		d, e, f = iota, iota, iota // 3
		g       = iota             // 4
	)

	fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}

func arrays() {
	a := [3]int{1, 2, 3}
	b := [10]int{1, 2, 3}  // 前三个为1，2，3其他默认为0
	c := [...]int{4, 5, 6} // 省略长度Go会自动根据元素个数来计算长度
	fmt.Printf("a.len=%d,b.len=%d,c.len=%d\n", len(a), len(b), len(c))

	doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
	easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
	fmt.Printf("doubleArray:%d,easyArray:%d\n", len(doubleArray), len(easyArray))
}

// 动态数组，slice总是指向一个底层array，可以像array一样，只是不需要长度，不是真正意义
// 当slice引用的元素变化时，它的所有引用都会改变该值
// len：长度,cap：最大容量
func slices() {
	slice := []byte{'a', 'b', 'c', 'd'}
	var ar = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
	var a, b []byte // a,b 为slice
	a = ar[2:5]
	b = ar[3:5]
	c := ar[:6] // 从0开始，取6个
	d := ar[:]  // 从0开始，取ar的长度
	fmt.Printf("slice.len=%d,ar.len=%d,a.len=%d,b.len=%d,c.len=%d,d.len=%d,d.cap=%d\n", len(slice), len(ar), len(a), len(b), len(c), len(d), cap(d))

	len1 := len(c)
	c1 := cap(c)
	c = append(c, 'k')
	len2 := len(c)
	c2 := cap(c)
	c = append(c, 'l')
	c = append(c, 'm')
	c = append(c, 'n')
	len3 := len(c)
	c3 := cap(c)
	c = append(c, 'o')
	len4 := len(c)
	c4 := cap(c)
	fmt.Printf("c.len1=%d,c.cap1=%d,c.len2=%d,c.cap2=%d,c.len3=%d,c.cap3=%d,c.len4=%d,c.cap4=%d\n", len1, c1, len2, c2, len3, c3, len4, c4)
}

// 字典
// map和其他基本型别不同，它不是thread-safe，在多个go-routine存取时，必须使用mutex lock机制
func maps() {
	var numbers map[string]int
	numbers = make(map[string]int)
	numbers["one"] = 1
	numbers["two"] = 2
	numbers["three"] = 3

	two := numbers["two"]
	numbers["two"] = 20 // 修改
	two2 := numbers["two"]

	fmt.Printf("two:%d,numbers.len=%d,two2:%d\n", two, len(numbers), two2)

	rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5, "C++": 2}
	// 返回两个值，不存在则第二个值为false
	csharpRating, ok := rating["C#"]
	if ok {
		fmt.Printf("C# is in the map an its rating is %f\n", csharpRating)
	} else {
		fmt.Printf("We have no rating associated with C# in the map\n")
	}
	len1 := len(rating)
	delete(rating, "C") // 删除C元素
	len2 := len(rating)
	fmt.Printf("the rating.len=%d,after del it's %d\n", len1, len2)

	// make可用于创建内建类型map、slice和channel的内存分配，返回一个有初始值（非零）的T类型，而不是*T，（导致这三个类型           有所有不同的原因是指向数据结构引用在使用前必须被初始化）
	// new用于各种类型的内存分配，new返回指针

	// for与range配合用于读取slice和map数据
	for k, v := range rating {
		fmt.Printf("%s=%f\n", k, v)
	}
}

func switchs() {
	integer := 6
	switch integer {
	case 4:
		fmt.Println("The integer was <= 4")
		fallthrough
	case 5:
		fmt.Println("The integer was <= 5")
		fallthrough
	case 6:
		fmt.Println("The integer was <= 6")
		fallthrough
	case 7:
		fmt.Println("The integer was <= 7")
		fallthrough
	case 8:
		fmt.Println("The integer was <= 8")
		fallthrough
	default:
		fmt.Println("default case")
	}
}

func SumAndProduct(A, B int) (int, int) {
	return A + B, A * B
}

// 官方建议：最好命名返回值，因为不命名返回值，虽然使得代码更加简洁了，但是会造成生成的文档可读性差
func SumAndProduct2(A, B int) (add int, multiplied int) {
	add = A + B
	multiplied = A * B
	return
}

// 变参
func myfunc(arg ...int) {
	for _, n := range arg {
		fmt.Printf("And the number is:%d\n", n)
	}
}

func add1(a *int) int {
	*a = *a + 1 // 修改了a的值
	return *a
}

func funcs() {
	x := 3
	y := 4

	xPlusy, xTimesy := SumAndProduct(x, y)
	fmt.Printf("%d + %d = %d\n%d * %d = %d\n", x, y, xPlusy, x, y, xTimesy)
	xPlusy2, xTimesy2 := SumAndProduct2(x, y)
	fmt.Printf("%d + %d = %d\n%d * %d = %d\n", x, y, xPlusy2, x, y, xTimesy2)

	myfunc(1, 2, 3, 4, 5)

	a := 3
	fmt.Printf("a=%d\n", a)
	a1 := add1(&a)
	fmt.Printf("a+1=%d,a=%d\n", a1, a)
}

func readFile() {
	filePath := "hello.txt"
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 066) // read access
	defer file.Close()
	if err != nil {
		fmt.Printf("open fail %v\n", err)
		return
	}
	fmt.Println("open ok")
}

// defer延迟，defer语句会按逆序执行，最后该函数返回
func defers() {
	readFile()

	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i) // 4 3 2 1 0
	}
	fmt.Println()
}

type testInt func(int) bool // 声明一个函数类型

func isOdd(integer int) bool {
	return integer%2 != 0
}

func isEven(integer int) bool {
	return integer%2 == 0
}

func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}

func testFuncDef() {
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("slice=", slice)
	odd := filter(slice, isOdd)
	even := filter(slice, isEven)
	fmt.Println("Odd elements of slice are:", odd)
	fmt.Println("Even elements of slice are:", even)
}

func inits() {
	var user = os.Getenv("USER")
	fmt.Println("get the os env USER=", user)
	if user == "" {
		panic("no value for $USER")
	}
	fmt.Println("the $USER is ", user)
}

func throwsPanic(f func()) (b bool) {
	defer func() {
		if x := recover(); x != nil {
			b = true
		}
	}()
	f() // 执行函数f，如果f中出现了panic，那么就可以恢复回来
	return
}

func panics() {
	throwsPanic(inits)
	fmt.Println("call the throws panic end.")
}

// ----------------------------------------
type person struct {
	name string
	age  int
}

// 比较两个年龄，返回年龄大的那个人，并返回年龄差
func Older(p1, p2 person) (person, int) {
	if p1.age > p2.age {
		return p1, p1.age - p2.age
	}
	return p2, p2.age - p1.age
}
func structs() {
	var p person
	p.name = "lee"
	p.age = 20
	fmt.Println("the person's name is ", p.name)

	bob := person{"Bob", 28}              // 按顺序提供初始化值
	paul := person{name: "Paul", age: 53} // 通过field:value方式初始化，可以任意顺序
	p3 := new(person)                     // 分配为指针,*person

	t_older, a_diff := Older(p, bob)
	t_older1, a_diff1 := Older(bob, paul)
	t_older2, a_diff2 := Older(p, paul)
	t_older3, a_diff3 := Older(p, *p3)
	fmt.Printf("Of %s and %s, %s is older by %d year\n", p.name, bob.name, t_older.name, a_diff)
	fmt.Printf("Of %s and %s, %s is older by %d year\n", bob.name, paul.name, t_older1.name, a_diff1)
	fmt.Printf("Of %s and %s, %s is older by %d year\n", p.name, paul.name, t_older2.name, a_diff2)
	fmt.Printf("Of %s and %s, %s is older by %d year\n", p.name, (*p3).name, t_older3.name, a_diff3)
}

// 匿名字段(所有内置类型和自定义类型都可以作为匿名字段）
// 当匿名字段是一个struct时，那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct
type Human struct {
	name   string
	age    int
	weight int
}
type Skills []string
type Student struct {
	Human      // 匿名字段，那么默认Student就包含了Human中所有字段
	Skills     // 匿名字段，自定义类型string slice
	int        // 内置类型作为匿名字段
	speciality string
}

// 如果Human中的字段与Student中字段同名，则以最外层优先访问
func hiddens() {
	//mark := Student{ Human{"lee",20,120}, "Computer Science"}
	mark := Student{Human: Human{"mark", 20, 120}, speciality: "Computer Science"}
	fmt.Println("His name is ", mark.name)
	fmt.Println("His age is ", mark.age)
	fmt.Println("His weight is ", mark.weight)
	fmt.Println("His speciality is ", mark.speciality)

	mark.speciality = "AI technology"
	mark.age = 28
	mark.weight += 60
	mark.Skills = []string{"anatomy"}
	mark.int = 100
	fmt.Println("the man become old.")
	fmt.Println("His name is ", mark.name)
	fmt.Println("His age is ", mark.age)
	fmt.Println("His weight is ", mark.weight)
	fmt.Println("his skills are ", mark.Skills)
	fmt.Println("His speciality is ", mark.speciality)
	fmt.Println("His preferred number is ", mark.int)

	mark.Human.weight = 130
	fmt.Println("His weight is ", mark.weight)

	HiddenCover()
}

type Person struct {
	name  string
	age   int
	phone string // Person类型拥有的字段
}
type Employee struct {
	Person     // 匿名字段Person
	speciality string
	phone      string // 雇员的phone字段
}

func HiddenCover() {
	bob := Employee{Person{"Bob", 32, "777-423-1235"}, "Designer", "321-789"}
	fmt.Println("Bob's work phone is:", bob.phone)
	fmt.Println("Bob's personal phone is:", bob.Person.phone)
}

// method
type Rectangle struct {
	width, height float64
}
type Circle struct {
	radius float64
}

func (r Rectangle) area() float64 {
	return r.width * r.height
}
func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}
func methods() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9.8, 3.5}
	c1 := Circle{10}
	c2 := Circle{25}

	fmt.Println("Area of r1 is: ", r1.area())
	fmt.Println("Area of r2 is: ", r2.area())
	fmt.Println("Area of c1 is: ", c1.area())
	fmt.Println("Area of c2 is: ", c2.area())

	methods2()
}

const (
	WHITE = iota
	BLACK
	BLUE
	RED
	YELLOW
)

type Color byte
type Box struct {
	width, height, depth float64
	color                Color
}
type BoxList []Box // a slice boxes
func (b Box) Volume() float64 {
	return b.width * b.height * b.depth
}

func (b *Box) SetColor(c Color) {
	b.color = c
}
func (bl BoxList) BiggestColor() Color {
	v := 0.00
	k := Color(WHITE)
	for _, b := range bl {
		if bv := b.Volume(); bv > v {
			v = bv
			k = b.color
		}
	}
	return k
}
func (bl BoxList) PaintItBlack() {
	for i := range bl {
		bl[i].SetColor(BLACK)
	}
}
func (c Color) String() string {
	strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
	return strings[c]
}

// 如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method，而不需要&V去调用这个method
// 如果一个method的receiver是T，你可以在一个*T类型的变量P上面调用这个method，而不需要 *P去调用这个method
func methods2() {
	boxes := BoxList{
		Box{4, 4, 4, RED},
		Box{10, 10, 1, YELLOW},
		Box{1, 1, 20, BLUE},
		Box{10, 10, 1, BLACK},
		Box{10, 30, 20, WHITE},
		Box{20, 20, 20, YELLOW},
	}
	fmt.Printf("We have %d boxes in our set\n", len(boxes))
	fmt.Println("The volume of the first one is ", boxes[0].Volume(), "cm³")
	fmt.Println("The color of the last one is ", boxes[len(boxes)-1].color.String())
	fmt.Println("The biggest one is ", boxes.BiggestColor().String())
	fmt.Println("Let's paint them all black")
	boxes.PaintItBlack()
	fmt.Println("The color of the second one is ", boxes[1].color.String())
	fmt.Println("Obviously, now, the biggest one is ", boxes.BiggestColor().String())
}
